frontend/pages/e/[uuid].tsx (view raw)
1import {useState, useReducer, useEffect} from 'react';
2import Box from '@material-ui/core/Box';
3import {makeStyles, useTheme} from '@material-ui/core/styles';
4import {useTranslation} from 'react-i18next';
5import {initializeApollo} from '../../lib/apolloClient';
6import useToastStore from '../../stores/useToastStore';
7import useEventStore from '../../stores/useEventStore';
8import Layout from '../../layouts/Default';
9import AddToMyEventDialog from '../../containers/AddToMyEventDialog';
10import TravelColumns from '../../containers/TravelColumns';
11import NewTravelDialog from '../../containers/NewTravelDialog';
12import VehicleChoiceDialog from '../../containers/VehicleChoiceDialog';
13import WelcomeDialog from '../../containers/WelcomeDialog';
14import EventBar from '../../containers/EventBar';
15import Loading from '../../containers/Loading';
16import OnBoardingTour from '../../containers/OnBoardingTour';
17import {
18 useUpdateEventMutation,
19 Event as EventType,
20 useEventByUuidQuery,
21 EventByUuidDocument,
22 EditEventInput,
23 useFindUserVehiclesQuery,
24} from '../../generated/graphql';
25import ErrorPage from '../_error';
26import useProfile from '../../hooks/useProfile';
27import Fab from '../../containers/Fab';
28import useMediaQuery from '@material-ui/core/useMediaQuery';
29
30const POLL_INTERVAL = 10000;
31
32interface Props {
33 event: EventType;
34 eventUUID: string;
35}
36
37const EventPage = props => {
38 const {t} = useTranslation();
39 const {event} = props;
40 if (!event) return <ErrorPage statusCode={404} title={t`event.not_found`} />;
41 return <Event {...props} />;
42};
43
44const Event = (props: Props) => {
45 const {eventUUID} = props;
46 const classes = useStyles();
47 const theme = useTheme();
48 const {t} = useTranslation();
49 const {user} = useProfile();
50 const {
51 data: {me: {profile: {vehicles = []} = {}} = {}} = {},
52 loading
53 } = useFindUserVehiclesQuery();
54 const addToast = useToastStore(s => s.addToast);
55 const setEvent = useEventStore(s => s.setEvent);
56 const eventUpdate = useEventStore(s => s.event);
57 const setIsEditing = useEventStore(s => s.setIsEditing);
58 const [updateEvent] = useUpdateEventMutation();
59 const [isAddToMyEvent, setIsAddToMyEvent] = useState(false);
60 const [openNewTravelContext, toggleNewTravel] = useState({opened: false});
61 const [openVehicleChoice, toggleVehicleChoice] = useReducer(i => !i, false);
62 const {data: {eventByUUID: event} = {}} = useEventByUuidQuery({
63 pollInterval: POLL_INTERVAL,
64 variables: {uuid: eventUUID},
65 });
66 const matches = useMediaQuery(theme.breakpoints.down('sm'));
67 const addCarClasses = matches ? 'tour_travel_add' : '';
68
69 useEffect(() => {
70 if (event) setEvent(event as EventType);
71 }, [event]);
72
73 const onSave = async e => {
74 try {
75 const {uuid, ...data} = eventUpdate;
76 const {id, __typename, travels, users, waitingList, ...input} = data;
77 await updateEvent({
78 variables: {uuid, eventUpdate: input as EditEventInput},
79 refetchQueries: ['eventByUUID'],
80 });
81 setIsEditing(false);
82 } catch (error) {
83 console.error(error);
84 addToast(t('event.errors.cant_update'));
85 }
86 };
87
88 const onShare = async () => {
89 if (!event) return null;
90 // If navigator share capability
91 if (!!navigator.share)
92 return await navigator.share({
93 title: `Caroster ${event.name}`,
94 url: `${window.location.href}`,
95 });
96 // Else copy URL in clipboard
97 else if (!!navigator.clipboard) {
98 await navigator.clipboard.writeText(window.location.href);
99 addToast(t('event.actions.copied'));
100 return true;
101 }
102 };
103
104 const addTravelClickHandler =
105 user && vehicles?.length != 0
106 ? toggleVehicleChoice
107 : () => toggleNewTravel({opened: true});
108
109 if (!event || loading) return <Loading />;
110
111 return (
112 <Layout
113 pageTitle={t('event.title', {title: event.name})}
114 menuTitle={t('event.title', {title: event.name})}
115 displayMenu={false}
116 >
117 <EventBar
118 event={event}
119 onAdd={setIsAddToMyEvent}
120 onSave={onSave}
121 onShare={onShare}
122 />
123 <TravelColumns toggle={addTravelClickHandler} />
124 <Box className={classes.bottomRight}>
125 <Fab
126 onClick={addTravelClickHandler}
127 aria-label="add-car"
128 color="primary"
129 className={addCarClasses}
130 >
131 {t('travel.creation.title')}
132 </Fab>
133 </Box>
134 <NewTravelDialog
135 context={openNewTravelContext}
136 toggle={() => toggleNewTravel({opened: false})}
137 />
138 <VehicleChoiceDialog
139 open={openVehicleChoice}
140 toggle={toggleVehicleChoice}
141 toggleNewTravel={toggleNewTravel}
142 vehicles={vehicles}
143 />
144 <AddToMyEventDialog
145 event={event}
146 open={isAddToMyEvent}
147 onClose={() => setIsAddToMyEvent(false)}
148 />
149 <WelcomeDialog />
150 <OnBoardingTour />
151 </Layout>
152 );
153};
154
155export async function getServerSideProps(ctx) {
156 const {uuid} = ctx.query;
157 const apolloClient = initializeApollo();
158 const {data = {}} = await apolloClient.query({
159 query: EventByUuidDocument,
160 variables: {uuid},
161 });
162 const {eventByUUID: event} = data;
163 const {host = ''} = ctx.req.headers;
164
165 return {
166 props: {
167 event,
168 eventUUID: uuid,
169 metas: {
170 title: event?.name || '',
171 url: `https://${host}${ctx.resolvedUrl}`,
172 },
173 },
174 };
175}
176
177const useStyles = makeStyles(theme => ({
178 bottomRight: {
179 position: 'absolute',
180 bottom: theme.spacing(1),
181 right: theme.spacing(6),
182 width: 200,
183 [theme.breakpoints.down('sm')]: {
184 right: theme.spacing(1),
185 },
186 },
187}));
188
189export default EventPage;